Behind The Scene: Integrating Ext Grid
Jumper Chen, Engineer, Potix Corporation
July 27, 2007
Applicable to zk-2.5.0-FL-2007-07-12 and later.
- Applicable to yuiextz 0.5.1.
Purpose
In the previous small talks ( Integrating Google Maps and Integrating FCKeditor), we introduced how to add a new component into ZK component. In this article, we’ll describe how to add a new component for Yui-Ext and how ZK client engine interact with Ext engine.
Live Demo
A few days ago, we wrote an article regarding the demo example of yuiextz component which could be found in the smalltalks New Features of yuiextz 0.5.1.
Introduction
To integrate Ext Grid, we need to prepare a data format supported by Ext Grid, such as HTML table, xml data, data array, and so forth. So far, we only render the output of Y-Grid component into HTML table according to the format of Ext Grid. The output is generated by child components of Y-Grid, including grid, columns, column, rows, and row. Thus, we have to prepare corresponding DSP (as JSP) files of these components to generate required html tags.
Architecture
The interaction between Ext JS and ZK client engine is described as follows. In this example, we should not interact with the DOM tree directly to avoid conflict between ZK client engine and Ext engine. Thus, we should only interact with Ext engine instead of the DOM tree. Now we’ll explain to you step by step.
The difference between ZK client engine and Ext engine
- ZK Client Engine:
- The key part of ZK client engine use the uuid of each component to identify the ZK component at client side. Actually, when the visiting page is loading, the ZK server engine will use the DSP language to produce the raw HTML text and to put the uuid of each component into the according HTML tag as id (One to One). Therefore, ZK client engine will use the id of HTML to manipulate the JavaScript object by ZK client engine and use the id to notify the server side object accordingly from browser to server.
- Ext JS:
- In the Ext JS, it uses JavaScript object to store its data model. In this case, it uses the Ext.grid.Grid object to operate the grid component in JavaScript. In this grid object, it uses the Ext.data.Record object to cache the row data and uses the Ext.grid.ColumnModel to store the information of header.
The four steps to register a new component
To implement a new component into ZK component.we need to prepare four types of files.
- The lang-addon.xml file: Register the new ZK component.
- The Java files (*.java): Component as Java objects.
- The template files (*.dsp): Template to generate final HTML tags.
- The Javascript files (*.js): Glue logic to link the Javascript component to the Java class.
Register a new component in lang-addon.xml file
We have to register the new component in lang-addon.xml file so that ZK loader will know how to identify it. In the file we define the following elements to describe the new component.
...
<javascript-module name="yuiextz.grid" version="0.5.1"/>
...
<component>
<component-name>grid</component-name>
<component-class>org.zkforge.yuiext.grid.Grid</component-class>
<mold>
<mold-name>default</mold-name>
<mold-uri>~./yuiextz/grid.dsp</mold-uri>
</mold>
</component>
...
- javascript-module:
- Specifies the dynamic JavaScript source(Load-on-demand). This source of JavaScript is used by Y-Grid component.
- component-name:
- Defines a component name as grid.
- component-class:
- Defines the component class is used by the component.
- mold:
- A component might have zero, one or multiple molds. If it doesn't have any mold, it has to take care of rendering by overriding the redraw method. If it does, the mold specified here will be used to render the component into HTML tags. The mold called default is the default mold.
Note:You could define as many as component as you want.
A Java object of Component: the Grid.java
The way to interact the Ext JS Grid at the client side in the Java Object depends on the smartUpdate() method to manipulate the JavaScript object of grid. Here we choose one as our example: The track-mouse-over function.
public boolean isTrackMouseOver() {
return _trackMouseOver;
}
public void setTrackMouseOver(boolean trackMouseOver) {
if (isEditable() && trackMouseOver)
throw new UiException("Appliable only to the uneditable type: "
+ this);
if (_trackMouseOver != trackMouseOver) {
_trackMouseOver = trackMouseOver;
smartUpdate("z.trackMouseOver", String.valueOf(trackMouseOver));
}
}
In ZK framework, we usually use the smartUpdate() method to send a command from server to browser side. It is used by the ZK server to send command back to the browser in an Ajax response. The first argument of the smartUpdate is the attribute name or the command name. The second argument is a String that is the new value of the attribute or the associated data of the command to be sent to the browser. E.g. The smartUpdate("z.trackMouseOver", "true") will send z.trackMouseOver command along with the boolean string (true) to the browser and tells the Ext Grid at the browser side to change status to true. Notice that for each http request, command with same name will be sent only once. Thus within one event handling (one XMLHttpRequest), if the Java API called the smartUpdate with same command name more than once, only the last one is sent.
Generate the HTML tags: the grid.dsp
A dsp file used in ZK is a template file similar to a typical jsp file. You can use the dsp tag libraries provided to generate the final HTML tags that will be read by the browser. We use an html
<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %>
<%@ taglib uri="/WEB-INF/tld/zk/core.dsp.tld" prefix="z" %>
<c:set var="self" value="${requestScope.arg.self}"/>
<div id="${self.uuid}" z.type="yuiextz.grid.ExtGrid"${self.outerAttrs}${self.innerAttrs} z.rows="${self.rows.uuid}" >
<table width="100%" border="0">
<c:if test="${!empty self.columns}">
<thead>
${z:redraw(self.columns, null)}
</thead>
</c:if>
${z:redraw(self.rows, null)}
</table>
</div>
In the dsp file, we use a special property to define a related JavaScript object.
- z.type:
- Using the pattern of name specifies JavaScript object.("PackageName.FileName.JavascriptObjectName")
Note: The name of JavaScript object must add a prefix name with zk such as (zkExtGrid) and yuiextz.grid is used to locate the js file under /web/js/yuiextz/grid.js by the Java classpath.
The catcher of the smartUpdate command
As we mentioned in Grid.java, to use the smartupdate() method control the behavior of grid. So ZK client engine will use JavaScript object which is predefine in the z.type property to catch the command of smartUpdate from server to browser side by the setAttr function as below.
zkExtGrid.setAttr = function (cmp, name, value) {
if(name == "z.trackMouseOver"){
var grid = zkau.getMeta($real(cmp)).grid;
if(value == "true"){
grid.on("mouseover", grid.view.onRowOver, grid.view);
grid.on("mouseout", grid.view.onRowOut, grid.view);
}else {
grid.un("mouseover", grid.view.onRowOver, grid.view);
grid.un("mouseout", grid.view.onRowOut, grid.view);
}
return true;
}
...
In the setAttr() functon, we use the if/else statement to check which name of command is we need. In this example, we need to catch the name with "z.trackMouseOver" and to control the Ext Grid API to register or unregister an envent.
The JavaScript component life cycle: init and cleanup
The HTML tags generated by ZK dsp language will not be recognized by Ext Grid, so we need to reorganize the result generated by Ext Grid in the initialization of ZK JavaScript component life cycle by ZK client engine invoking its init() function to reorganize the Ext Grid HTML structure.
zk.addModuleInit(function () {
zkExtGrid.init = function (cmp) {
...
var grid = getZKAttr(cmp, "gridType") == "tableGrid" ?
new zkExtGrid._tableGrid(cmp,zkExtGrid._config(cmp)) :
new zkExtGrid._editorGrid(cmp,zkExtGrid._config(cmp)) ;
grid.container.id = cmp.id;
grid.container.dom.id = cmp.id;
grid.node = cmp; // store the first HTML dom of grid component.
grid.view = new zkExtGridView(); // reorganize the data structure of HTML
cmp.grid = grid;
zkau.setMeta($real(cmp),cmp);// store the relation of cmp and grid
...
};
});
In the above code, you see that we use the zkExtGridView() function to reorganize the Ext Grid HTML structure.
When the component will be destroyed, ZK client engine will invoke its cleanup() function to clean the component as below,
zkExtGrid.cleanup = function (cmp) {
var gcmp = zkau.getMeta($real(cmp));
if(gcmp)gcmp.grid.destroy();
};
In this sample, we use the destory() function of Ext Grid to clean the component entity.
Attach UUID of ZK component to Ext Grid
The following fragment code is in the zkExtGridView() function.
tpls.master = new Ext.Template(
'<div class="x-grid" hidefocus="true">',
'<div id="' + grid.container.id + '!topbar" class="x-grid-topbar"></div>',
'<div id="' + grid.container.id + '!scroller" class="x-grid-scroller"><div></div></div>',
'<div id="' + grid.container.id + '!locked" class="x-grid-locked">',
'<div class="x-grid-header">{lockedHeader}</div>',
'<div class="x-grid-body">{lockedBody}</div>',
"</div>",
'<div id="' + grid.container.id + '!real" class="x-grid-viewport ' + attrs.cls + '"' + attrs.attrs + ' ' + attrs.style + ' >',
'<div class="x-grid-header">{header}</div>',
'<div class="x-grid-body">{body}</div>',
"</div>",
'<div id="' + grid.container.id + '!bottombar" class="x-grid-bottombar"></div>',
'<a href="#" class="x-grid-focus" tabIndex="-1"></a>',
'<div id="' + grid.container.id + '!proxy" class="x-grid-resize-proxy"> </div>',
"</div>"
);
As you can see, we glue the id of ZK component to Ext Grid's template manually. ZK client engine can use this id to find the ZK client component. And this way can’t break the behavior of Ext Grid.
Send command from browser to server: the zkau.send() method
The event registration code is done in zkExtGrid.init method.
zk.addModuleInit(function () {
zkExtGrid.init = function (cmp) {
...
grid.colModel.on('columnmoved', zkExtGrid.onColumnMoved, grid.colModel);
...
};
});
We have registered an columnmoved event in above example. The event will notify the Ext Grid listener when user to move the column on the grid, and then we use ZK client engine to send the event to notify the ZK server component as follows,
zkExtGrid.onColumnMoved = function (cm, oldIndex, newIndex){
var cmp = cm.cmp;
zkau.send({uuid: cmp.id, cmd: "onColumnMoved",
data: [cm.getDataIndex(newIndex),oldIndex,newIndex]},
zkau.asapTimeout(cmp, "onColumnMoved", 10));
};
The zkau.send function is used to send a commend with a necessary data array from browser to server via an Ajax request. To use the zkau.send function, you must specify two arguments. The first argument is including, uuid, cmd, and data. The uuid is a id of JavaScript component accordingly. The cmd is a name of command at server side. The data is a necessary data array for this event. The second argument is a number of millisecond regarding whether how soon this command should be sent to the server.
The catcher of the onColumnMoved command on server side
The catcher (ZK update engine) of the command at the server side will first wrap the command into an AuRequest object then based on its command name (in this case, the onColumnMoved), delegate to the specific command processor. In this case, to process the passed back onColumnMoved command, we need a ColumnMovedCommand command processor to update the order of moved column.
public class ColumnMovedCommand extends Command {
...
protected void process(AuRequest request) {
final Component comp = request.getComponent();
final String[] data = request.getData();
...
final Grid grid = (Grid) comp;
final Columns cols = grid.getColumns();
final Rows rows = grid.getRows();
final Desktop desktop = request.getDesktop();
final Column col = (Column) desktop.getComponentByUuid(data[0]);
final int oldIndex = Integer.parseInt(data[1]);
final int newIndex = Integer.parseInt(data[2]);
try {
((Updatable) (cols).getExtraCtrl()).setResult(Boolean.TRUE);
if (newIndex == cols.getChildren().size() - 1) {
cols.insertBefore(col, null);
} else {
cols.insertBefore(col, (Column) cols.getChildren().get(
oldIndex < newIndex ? newIndex + 1 : newIndex));
}
} finally {
((Updatable) (cols).getExtraCtrl()).setResult(Boolean.FALSE);
}
...
Events.postEvent(new ColumnMovedEvent(getId(), comp, col, Integer
.parseInt(data[1]), Integer.parseInt(data[2])));
}
}
This class extends Command and overrides process() method to handle the passed in AuRequest. As you can see, the target component has been fetched back by its uuid when wrapping the command into an AuRequest object. The data set is converted into a String array as the form it was prepared in zkau.send() Javascript method. After updating the order of moved column, it posts a ColumnMovedEvent to the ZK event queue so application developers can register the event and handle it.
Notice that to make the ZK update engine aware of a new command processor, you have to register it into the ZK's command processing map. The onXxx command name is where all things are associated together.
public class Grid extends XulElement {
...
static {
new ColumnMovedCommand("onColumnMoved", 0);
}
...
}
Summary
We welcome your contribution to integrate more Ext JS components into ZK framework. If you come up with any problem, feel free to ask us on ZK forum.
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |